//
// Created by QU on 24-4-22.
//

#include <iostream>
#include <memory>
#include <utility>          // for std::pair
#include <string>

using namespace std;
using std::cout, std::endl, std::cin;

int main([[maybe_unused]] int argc, [[maybe_unused]] const char *const argv[]) {
    allocator<string> allocator;
    const int allocator_size = 10;

    // return pointer point to uninitialized mem block;
    auto const allocate = allocator.allocate(allocator_size);

    auto q = allocate;
    auto r = allocate;

    allocator.construct(q++);
    allocator.construct(q++, 10, 'c');
    allocator.construct(q++, "hi");
    // 不能超过n个

    while (q != allocate) {
        cout << *(--q) << " ";
        allocator.destroy(q);
    }

    allocator.deallocate(allocate, allocator_size);

    allocator.allocate(q - r);

//    uninitialized_copy()
//    uninitialized_copy_n()
//    uninitialized_fill()

}


// 这里使用的全部是拷贝的版本, 正常情况下, 有相当的部分可以改造成为std::move相关, 减少内存拷贝.
class StrVec {
public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}

    StrVec(const StrVec &);

    StrVec(StrVec && strVec) noexcept;

    StrVec &operator=(const StrVec &);

    StrVec(std::initializer_list<std::string> initializerList);

    ~StrVec();

    void push_pack(const std::string &);

    size_t size() const { return first_free - elements; }

    size_t capacity() const { return cap - elements; }

    std::string *begin() const { return elements; }

    std::string *end() const { return first_free; }


private:
    static std::allocator<std::string> allocator;

    void chk_n_alloc() { if (size() == capacity()) reallocate(); }

    std::pair<std::string *, std::string *> alloc_n_copy(const std::string *, const std::string *);

    void free();

    inline void reallocate();

    std::string *elements;
    std::string *first_free;
    std::string *cap;
};

void StrVec::push_pack(const string &s) {
    // check
    chk_n_alloc();
    // at last have 1 capacity to save string.
    // first_free should save back(next) of the end of value, so back++ of param.
    allocator.construct(first_free++, s);
}

std::pair<std::string *, std::string *> StrVec::alloc_n_copy(const std::string *begin, const std::string *end) {
    auto data = allocator.allocate(end - begin);
    // allocate() returns a uninitialized mem.

    // using uninitialized_copy to initialized mem block.
    return std::pair<std::string *, std::string *>{data, uninitialized_copy(begin, end, data)};
}

void StrVec::free() {
    if (elements) {
        for (auto p = first_free; p != elements; /* null  */) {
            // must destroy firstly
            allocator.destroy(--p);
        }
        // then de-allocate allocated mem block with numbers you have apply to allocator.
        allocator.deallocate(elements, cap - elements);
    }
}

void StrVec::reallocate() {
    auto newcapacity = size() ? 2 * size() : 1;
    auto newdata = allocator.allocate(newcapacity);
    auto dest = newdata;
    auto elem = elements;

    for (size_t i = 0; i != size(); ++i) {
        allocator.construct(dest++, std::move(*elem++));
    }
    free();

    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

StrVec::StrVec(const StrVec &s) {
    auto newdata = alloc_n_copy(s.begin(), s.end());
    this->elements = newdata.first;
    this->first_free = this->cap = newdata.second;
}

StrVec::~StrVec() { free(); }

StrVec &StrVec::operator=(const StrVec &orig) {
    // allocate same capacity of mem block.
    auto data = alloc_n_copy(orig.begin(), orig.end());
    free();

    this->elements = data.first;
    this->first_free = this->cap = data.second;
    return *this;
}

StrVec::StrVec(std::initializer_list<std::string> initializerList) {
    auto newdata = alloc_n_copy(initializerList.begin(), initializerList.end());
    this->elements = newdata.first;
    this->first_free = this->cap = newdata.second;
}

StrVec::StrVec(StrVec &&strVec)  noexcept : elements(strVec.elements), first_free(strVec.first_free), cap(strVec.cap) {
    strVec.elements = strVec.first_free = strVec.cap = nullptr;
}
